#ifndef DMC_H_INCLUDED
#define DMC_H_INCLUDED

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Dynamic Memory Check (DMC) */

#ifndef NULL
#define NULL ((void *)0)
#endif

/* define real function */
#define malloc_real     malloc
#define free_real       free
#define memcpy_real     memcpy
#define printf_real     printf
#define exit_real       exit

/* print */
#define dmc_print(fmt, args...) printf_real("DCM: File:%s, Line:%d, Function:%s==> " fmt, \
                            __FILE__, __LINE__, __FUNCTION__, ## args) \

#define dmc_died_print(fmt, args...) printf_real("DCM: File:%s, Line:%d, Function:%s==> " fmt, \
                            __FILE__, __LINE__, __FUNCTION__, ## args); \
                            exit_real(EXIT_FAILURE )\
/* define */
#define $define(type, name) type (name) = NULL;
#define $define2(type, name, value) type (name) = (value);

/* malloc & free */
#define $free(name) \
    do { \
        if ((name) == NULL) { \
            dmc_print("variable:%s must not be NULL!\n", #name); \
        } else { \
            free_real((name)); \
            (name) = NULL; \
        } \
    } while(0);

#define $malloc(name, size) \
    do { \
        if ((name) != NULL) {\
            dmc_print("variable:%s must be NULL!\n", #name); \
            $free((name)); \
        } \
        (name) = malloc_real((size)); \
    } while(0);

#define $malloc2(type, name, size) $define(type, (name)) \
    $malloc((name), (size))

/* function start maybe return a pointer */
#define $$ {int __stack_enter;

/* function return with check variable in stack */
#define $return(name) \
    do {\
        int __stack_leave; \
        if ((unsigned char *)&__stack_leave < (unsigned char *)(name) && \
            (unsigned char *)(name) < (unsigned char *)&__stack_enter) { \
            dmc_died_print("variable:%s will be langling pointer!\n", #name); \
        } \
        return (name);\
    }while (0);

/* access memory directly */
#define $read(name, max, idx, value) \
    do { \
        if ((idx) >= (max) || (idx) < 0) { \
            dmc_died_print("variable:%s index:%d access out of boundary!\n", #name, (idx)); \
        } \
        value = name[(idx)]; \
    } while(0);

#define $readptr(name, max, ptr, value) \
    do { \
        if (!(ptr)) { \
            dmc_died_print("variable:%s ptr null!\n", #name); \
        } \
        if (!((ptr) >= (name) && (ptr) < ((name) + (max)))) { \
            dmc_died_print("variable:%s access out of boundary!\n", #name); \
        } \
        value = *(ptr); \
    } while(0);

#define $write(name, max, idx, value) \
    do { \
        if ((idx) >= (max) || (idx) < 0) { \
            dmc_died_print("variable:%s index:%d access out of boundary!\n", #name, (idx)); \
        } \
        name[(idx)] = value; \
    } while(0);

#define $writeptr(name, max, ptr, value) \
    do { \
        if (!(ptr)) { \
            dmc_died_print("variable:%s ptr null!\n", #name); \
        } \
        if (!((ptr) >= (name) && (ptr) < ((name) + (max)))) { \
            dmc_died_print("variable:%s access out of boundary!\n", #name); \
        } \
        unsigned long __value = (unsigned long)value; \
        memcpy_real((void *)(ptr), (void *)&__value, sizeof(name[0])); \
    } while(0);

/* define a bound, then use bound to limit variable */
typedef struct {
    void *addr;             /* memory addr */
    unsigned long size;     /* signal element size */
    unsigned long count;    /* element count */
    unsigned long limit;    /* internal: size * count */
} __dmc_bound_t;

/* define a bound */
#define $bound(name, addr, size, count) \
    __dmc_bound_t name = {(void *)addr, \
    (unsigned long)(size), (unsigned long)(count), \
    (unsigned long)(count) * (unsigned long)(size)};

/* access memory with bound */
#define $get(bound, idx, value) \
    do { \
        if ((idx) < 0 || idx >= (bound).count) { \
            dmc_died_print("bound:%s index:%d access out of boundary!\n", #bound, (idx)); \
        } \
        memcpy_real((void *)&(value), (void *)((unsigned char *) (bound).addr + \
                    (idx) * (bound).size), (bound).size); \
    } while(0);

#define $getptr(bound, ptr, value) \
    do { \
        if (!(ptr)) { \
            dmc_died_print("bound:%s pointer null!\n", #bound); \
        } \
        if (!((unsigned char *)(ptr) >= (unsigned char *)(bound).addr && \
            (unsigned char *)(ptr) < ((unsigned char *)(bound).addr + (bound).limit))) { \
            dmc_died_print("bound:%s ptr:%p access out of boundary!\n", \
                           #bound, (void *)(ptr)); \
        } \
        memcpy_real((void *)&value, (void *)(ptr), (bound).size); \
    } while(0);

#define $set(bound, idx, value) \
    do { \
        if ((idx) < 0 || idx >= (bound).count) { \
            dmc_died_print("bound:%s index:%d access out of boundary!\n", #bound, (idx)); \
        } \
        unsigned long __tmp = (value); \
        memcpy_real((void *) (bound).addr + (idx) * (bound).size, (void *)&__tmp, (bound).size); \
    } while(0);

#define $setptr(bound, ptr, value) \
    do { \
        if (!(ptr)) { \
            dmc_died_print("bound:%s pointer null!\n", #bound); \
        } \
        if (!((unsigned char *)(ptr) >= (unsigned char *)(bound).addr && \
            (unsigned char *)(ptr) < ((unsigned char *)(bound).addr + (bound).limit))) { \
            dmc_died_print("bound:%s pointer:%p access out of boundary!\n", \
                           #bound, (void *)ptr); \
        } \
        unsigned long __tmp = (value); \
        memcpy_real((void *)(ptr), (void *)&__tmp, (bound).size); \
    } while(0);

/* abbreviation for function */
#define $rd(name, max, idx, value)  $read(name, max, idx, value)
#define $rdp(name, max, ptr, value) $readptr(name, max, ptr, value)
#define $wr(name, max, idx, value)  $write(name, max, idx, value)
#define $wrp(name, max, ptr, value) $writeptr(name, max, ptr, value)
#define $ret(name)                  $return(name)
#define $def(name)                  $define(name)
#define $getp(bound, ptr, value)    $getptr(bound, ptr, value)
#define $setp(bound, ptr, value)    $setptr(bound, ptr, value)

#endif // DMC_H_INCLUDED
